// 基于 tedis 封装自己的业务
import RedisOpts from './index';
import {httpClient} from '@srcUtils/req';
import {Buffer} from 'buffer';
import {User} from '@srcDb/model/user';
import {IAppInfo} from '@srcTypes/app-info';
import {AuthUtils} from '@srcUtils/utils';
import {lark} from '@/config';

export interface IAppTokenRes {
    app_access_token: string;
    tenant_access_token: string;
    code: number;
    msg: string;
    expire: number;
}

// 返回结构固定为 object ：{
//     "app_access_token": "xxxx",
//     "code": 0,
//     "expire": 7200,
//     "msg": "ok",
//     "tenant_access_token": "xxxxx"
// }
export const doQueryTenantToken = (appInfo: IAppInfo): Promise<IAppTokenRes> => {
    const {app_id, app_secret} = appInfo;
    return new Promise(async (resolve, reject) => {
        const tokenData = await RedisOpts.get(app_id);
        if (tokenData === null || tokenData === undefined) {
            const content = await httpClient.post({
                url: lark.env.api.AUTH_OPEN_API.APP_ACCESS_TOKEN_INTERNAL,
                requireToken: false,
                data: {
                    app_id,
                    app_secret,
                },
            });
            const contentData = content['data'];
            const expire = contentData.expire;
            await RedisOpts.setex(app_id, expire, JSON.stringify(contentData));
            resolve(contentData);
            return;
        }
        resolve(JSON.parse(tokenData as string));
    });
};

// 适应于没有 app secret 的场景
export const getTokenByAppId = (app_id: string, tokenType: string): Promise<string> => (new Promise<string>(async (resolve, reject) => {
    const result = await RedisOpts.get(app_id);
    const tokenRes: IAppTokenRes = JSON.parse(result as string) as IAppTokenRes;
    if (tokenType === 'tenant' || tokenType === 'T') {
        resolve(tokenRes.tenant_access_token);
        return;
    } else if (tokenType === 'app' || tokenType === 'A') {
        resolve(tokenRes.app_access_token);
        return;
    }
    reject(new Error('token 类型错误！'));
}));

// 有些场景只需要 token
export const getToken = (authorization: string, tokenType: string, isTest: boolean = false): Promise<string> => (new Promise<string>(async (resolve, reject) => {
    let appInfo: IAppInfo;
    if (isTest) {
        appInfo = {
            app_id: lark.env.appId,
            app_secret: lark.env.appSecret,
        };
    } else {
        const encodeAppInfo = Buffer.from(authorization.split(' ').pop(), 'base64').toString('utf-8');
        appInfo = JSON.parse(encodeAppInfo);
    }
    const result = await doQueryTenantToken(appInfo);
    if (tokenType === 'tenant' || tokenType === 'T') {
        resolve(result.tenant_access_token);
        return;
    } else if (tokenType === 'app' || tokenType === 'A') {
        resolve(result.app_access_token);
        return;
    }
    reject(new Error('token 类型错误！'));
}));

// key = app_id
// 保存结构固定为 string ：{
//  "app_access_token": "xxxx",
//  "code": 0,
//  "expire": 7200,
//  "msg": "ok",
//  "tenant_access_token": "xxxxx"
// }
// 返回结果 bool  true/false 成功/失败
export const saveTenantToken = (key: string, value: IAppTokenRes): Promise<any> => {
    return new Promise(async (resolve, reject) => {
        const {expire} = value;
        const appTokenRes = await RedisOpts.get(key);
        if (appTokenRes !== undefined) {
            // 已经存在过了
            resolve(false);
            return;
        }
        await RedisOpts.setex(key, expire, JSON.stringify(value));
        resolve(true);
    });
};

// 只能保存，不能获取
export const saveUserInfo = (userInfo: User, uuid: string): Promise<User | null> => {
    return new Promise(async (resolve, reject) => {
        const {expires_in} = userInfo;
        const userObj = await RedisOpts.get(uuid);
        if (userObj === null || userObj === undefined) {
            const user: User = JSON.parse(userObj as string) as User;
            await RedisOpts.setex(uuid, expires_in, JSON.stringify(userInfo));
            resolve(user);
        }
        resolve(userInfo);
    });
};

export const queryUserInfo = (uuid: string): Promise<User | null> => {
    return new Promise(async (resolve, reject) => {
        const userObj = await RedisOpts.get(uuid);
        if (userObj === null) {
            resolve(null);
            return;
        }
        const user: User = JSON.parse(userObj as string) as User;
        resolve(user);
    });
};
// 正常逻辑 调用该方法前，一定是已经获取过 tenant_token
export const getJsTicket = async (uuid: string, auth: string, type: string): Promise<{ open_id: string, ticket: string }> => {
    return new Promise(async (resolve, reject) => {
        if (type === 'app') {
            // 应用身份
            const {app_id} = AuthUtils.handleAuth(auth);
            const key = `ticket_${app_id}`;
            const jsTicket = await RedisOpts.get(key);
            if (jsTicket !== null) {
                // 有缓存
                resolve({open_id: '', ticket: jsTicket as string});
                return;
            }
            const tenantToken = await getTokenByAppId(app_id, 'T');
            const ticketRes = await httpClient.post({
                url: lark.env.api.AUTH_OPEN_API.JSSDK_TICKET,
                requireToken: true,
                token: tenantToken,
            });
            const ticketContent = (ticketRes as any).data.data;
            const {expire_in, ticket} = ticketContent;
            await RedisOpts.setex(key, expire_in, ticket);
            resolve({open_id: '', ticket: ticket});
            return;
        }
        // 用户身份
        const user: User = await queryUserInfo(uuid);
        const key = `u_ticket_${user.open_id}`;
        const jsTicket = await RedisOpts.get(key);
        if (jsTicket !== null) {
            resolve({open_id: user.open_id, ticket: jsTicket as string});
            return;
        }
        const ticketRes = await httpClient.post({
            url: lark.env.api.AUTH_OPEN_API.JSSDK_TICKET,
            requireToken: true,
            token: user.access_token,
        });
        const ticketContent = (ticketRes as any).data.data;
        const {ticket, expire_in} = ticketContent;
        await RedisOpts.setex(key, expire_in, ticket);
        resolve({open_id: user.open_id, ticket: ticket});
    });
};

